package com.atlassian.jira.issue.statistics;

import java.util.*;

import org.apache.commons.collections.set.ListOrderedSet;

import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.web.bean.PercentageGraphModel;

/**
 * Organizes the administration of the nested criterions.
 * First one is the primary criterion that contains nested secondaries.
 * Secondaries list issues underneath.
 */
public final class ExtendedStatsGroup extends StatsGroup
{
	private final StatisticsMapper primaryMapper;
	private final Map primaryHeadings;
	private final Map primaryValuesCache;
	private final StatisticsMapper secondaryMapper;
	private final Map secondaryHeadings;
	private final Map secondaryValuesCache;

	/**
	 * Main constructor.
	 *
	 * @param primaryMapper the <code>StatisticsMapper</code> for the primary search criterion
	 * @param secondaryMapper the <code>StatisticsMapper</code> for the secondary search criterion
	 */
	public ExtendedStatsGroup(StatisticsMapper primaryMapper, StatisticsMapper secondaryMapper)
	{
		super(primaryMapper);
		primaryValuesCache = new HashMap();
		this.primaryMapper = primaryMapper;
		primaryHeadings = new TreeMap(primaryMapper.getComparator());
		secondaryValuesCache = new HashMap();
		this.secondaryMapper = secondaryMapper;
		secondaryHeadings = new TreeMap(primaryMapper.getComparator());
	}

	/**
	 * Adds the issue to the appropriate map that organize the nesting of the search parameters
	 * @param primaryHeading the primary search parameter
	 * @param secondaryHeading the socondary search parameters that is nested under the primary one
	 * @param issue the <code>Issue</code> to be added
	 */
	public void addValue(String primaryHeading, String secondaryHeading, Issue issue)
	{
		TreeMap secondaryHeadingValueMap;
		Object primaryHeadingValue = getPrimaryHeadingValue(primaryHeading);
		Object secondaryHeadingValue = getSecondaryHeadingValue(secondaryHeading);

		if( !primaryMapper.isValidValue(primaryHeadingValue) )
			return;
		if ( !primaryHeadings.containsKey(primaryHeadingValue))
		{
			primaryHeadings.put(primaryHeadingValue, primaryHeadingValue);
			secondaryHeadingValueMap = new TreeMap(secondaryMapper.getComparator());
		}
		else
		{
			secondaryHeadingValueMap = (TreeMap) secondaryHeadings.get(primaryHeadingValue);
		}

		if( !secondaryMapper.isValidValue(secondaryHeadingValue) )
			return;
		Collection currentSecondaryValues = (Collection) secondaryHeadingValueMap.get(secondaryHeadingValue);
		if( currentSecondaryValues == null )
			currentSecondaryValues = new ListOrderedSet();
		currentSecondaryValues.add(issue);

		secondaryHeadingValueMap.put(secondaryHeadingValue, currentSecondaryValues);
		secondaryHeadings.put(primaryHeadingValue, secondaryHeadingValueMap);
	}

	/**
	 * Gets the primaryHeading from the cache and adds it if it has not been cashed before
	 * @param primaryHeading the primary search parameter
	 * @return returns the cached primaryHeading value
	 */
	private Object getPrimaryHeadingValue(String primaryHeading)
	{
		Object primaryHeadingValue = primaryValuesCache.get(primaryHeading);
		if( primaryHeadingValue == null )
		{
			primaryHeadingValue = primaryMapper.getValueFromLuceneField(primaryHeading);
			primaryValuesCache.put(primaryHeading, primaryHeadingValue);
		}
		return primaryHeadingValue;
	}

	/**
	 * Gets the secondaryHeading from the cache and adds it if it has not been cashed before
	 * @param secondaryHeading the secondary search parameter
	 * @return returns the cached secondaryHeading value
	 */
	private Object getSecondaryHeadingValue(String secondaryHeading)
	{
		Object secondaryHeadingValue = secondaryValuesCache.get(secondaryHeading);
		if( secondaryHeadingValue == null )
		{
			secondaryHeadingValue = secondaryMapper.getValueFromLuceneField(secondaryHeading);
			secondaryValuesCache.put(secondaryHeading, secondaryHeadingValue);
		}
		return secondaryHeadingValue;
	}

	/**
	 * Returns all primary values
	 * @return a <code>Map</code> of all values delivered by the primary serach criterion
	 */
	public Map primarySet()
	{
		return primaryHeadings;
	}

	/**
	 * Returns all secondary values
	 * @return a <code>Map</code> of all values delivered by the secondary serach criterion
	 */
	public Map secondarySet()
	{
		return secondaryHeadings;
	}

	/**
	 * Overrides method in <code>com.atlassian.jira.issue.statistics.StatsGroup</code>
	 * All the issues will be shown under the secondary in the report
	 * @return a <code>Set</code> of all secondary values
	 */
	public Set entrySet()
	{
		return secondaryHeadings.entrySet();
	}

	/**
	 * Displays all resolved issues by primary criterion
	 * @param primaryHeadingValue the primary group-by criterion
	 * @return an <code>PercentageGraphModel</code> to display all resolved issues by primary criterion
	 */
	public PercentageGraphModel getPrimaryResolvedIssues(Object primaryHeadingValue)
	{
		PercentageGraphModel model = new PercentageGraphModel();
		long allIssuesSize = 0;
		long openIssuesSize = 0;
		if (secondaryHeadings.containsKey(primaryHeadingValue))
		{
			TreeMap secondaryHeadingValueMap = (TreeMap) secondaryHeadings.get(primaryHeadingValue);
			Set keySet = (Set) secondaryHeadingValueMap.keySet();
			Iterator it = keySet.iterator();
			while (it.hasNext())
			{
				Object secondaryHeadingValue = it.next();
				allIssuesSize = allIssuesSize + getAllIssueCount((Collection) secondaryHeadingValueMap.get(secondaryHeadingValue));
				if( allIssuesSize != 0L )
				{
					openIssuesSize = openIssuesSize + getOpenIssueCount((Collection) secondaryHeadingValueMap.get(secondaryHeadingValue));
					model = new PercentageGraphModel();
					model.addRow("009900", allIssuesSize - openIssuesSize, "Resolved Issues", null);
					model.addRow("cc0000", openIssuesSize, "Unresolved Issues", null);
					return model;
				}
			}
		}
		return model;
	}

	/**
	 * Displays all resolved issues by secondary criterion
	 * @param primaryHeadingValue the primary group-by criterion
	 * @param secondaryHeadingValue the secondary group-by criterion
	 * @return an <code>PercentageGraphModel</code> to display all resolved issues by secondary criterion
	 */
	public PercentageGraphModel getSecondaryResolvedIssues(Object primaryHeadingValue, Object secondaryHeadingValue)
	{
		PercentageGraphModel model = new PercentageGraphModel();
		if (secondaryHeadings.containsKey(primaryHeadingValue))
		{
			TreeMap secondaryHeadingValueMap = (TreeMap) secondaryHeadings.get(primaryHeadingValue);
			if (secondaryHeadingValueMap.containsKey(secondaryHeadingValue))
			{
				long allIssuesSize = getAllIssueCount((Collection) secondaryHeadingValueMap.get(secondaryHeadingValue));
				if( allIssuesSize != 0L )
				{
					long openIssuesSize = getOpenIssueCount((Collection) secondaryHeadingValueMap.get(secondaryHeadingValue));
					model = new PercentageGraphModel();
					model.addRow("009900", allIssuesSize - openIssuesSize, "Resolved Issues", null);
					model.addRow("cc0000", openIssuesSize, "Unresolved Issues", null);
					return model;
				}
			}
		}
		return model;
	}

	/**
	 * Counts the issues.
	 * @param issues as a <code>Collection</code>
	 * @return the count result
	 */
	public long getAllIssueCount(Collection issues)
	{
		if( issues == null || issues.isEmpty() )
			return 0L;
		else
			return (long) issues.size();
	}

	/**
	 * Counts all resoved issues.
	 * @param issues as a <code>Collection</code>
	 * @return the count result
	 */
	public long getResolvedIssueCount(Collection issues)
	{
		if( issues == null || issues.isEmpty() )
			return 0L;
		else
			return (long) issues.size() - getOpenIssueCount(issues);
	}

	/**
	 * Counts all open issues.
	 * Used to compute the number of the resolved issues.
	 * @param issues as a <code>Collection</code>
	 * @return the count result
	 */
	public long getOpenIssueCount(Collection issues)
	{
		if( issues == null || issues.isEmpty() )
			return 0L;
		long matchingIssues = 0L;
		Iterator iterator = issues.iterator();
		do
		{
			if( !iterator.hasNext() )
				break;
			Issue issue = (Issue) iterator.next();
			if( issue.getResolution() == null )
				matchingIssues++;
		}
		while( true );
		return matchingIssues;
	}

	/**
	 * Counts all issues by primary group-by criterion
	 * @param primaryHeadingValue the primary group-by criterion
	 * @return the count result
	 */
	public long getPrimaryAllIssueCount(Object primaryHeadingValue)
	{
		long retValue = 0;
		if (secondaryHeadings.containsKey(primaryHeadingValue))
		{
			TreeMap secondaryHeadingValueMap = (TreeMap) secondaryHeadings.get(primaryHeadingValue);
			Set keySet = (Set) secondaryHeadingValueMap.keySet();
			Iterator it = keySet.iterator();
			while (it.hasNext())
			{
				Object secondaryHeadingValue = it.next();
				Collection coll = (Collection) secondaryHeadingValueMap.get(secondaryHeadingValue);
				retValue = retValue + getAllIssueCount(coll);
			}
		}
		return retValue;
	}

	/**
	 * Counts all resolved issues by primary group-by criterion
	 * @param primaryHeadingValue the primary group-by criterion
	 * @return the count result
	 */
	public long getPrimaryResolvedIssueCount(Object primaryHeadingValue)
	{
		long retValue = 0;
		if (secondaryHeadings.containsKey(primaryHeadingValue))
		{
			TreeMap secondaryHeadingValueMap = (TreeMap) secondaryHeadings.get(primaryHeadingValue);
			Set keySet = (Set) secondaryHeadingValueMap.keySet();
			Iterator it = keySet.iterator();
			while (it.hasNext())
			{
				Object secondaryHeadingValue = it.next();
				Collection coll = (Collection) secondaryHeadingValueMap.get(secondaryHeadingValue);
				retValue = retValue + getResolvedIssueCount(coll);
			}
		}
		return retValue;
	}

	/**
	 * Returns the mapper of the primary criterion
	 * @return the <code>StatisticsMapper</code>
	 */
	public StatisticsMapper getPrimaryMapper()
	{
		return primaryMapper;
	}

	/**
	 * Returns the mapper of the secondary criterion
	 * @return the <code>StatisticsMapper</code>
	 */
	public StatisticsMapper getSecondaryMapper()
	{
		return secondaryMapper;
	}

}